#ifndef _DAG_H_
#define _DAG_H_
/*
 *  dag.h
 *  DAG
 *
 *  Created by Phillip Popp on 2/6/11.
 *  Copyright 2011 Phillip Popp. All rights reserved.
 *
 */

/* Generic helper definitions for shared library support */
#if defined _WIN32 || defined __CYGWIN__
#	define DAG_HELPER_DLL_IMPORT __declspec(dllimport)
#	define DAG_HELPER_DLL_EXPORT __declspec(dllexport)
#	define DAG_HELPER_DLL_LOCAL
#else
#	if __GNUC__ >= 4
#		define DAG_HELPER_DLL_IMPORT __attribute__ ((visibility("default")))
#		define DAG_HELPER_DLL_EXPORT __attribute__ ((visibility("default")))
#		define DAG_HELPER_DLL_LOCAL  __attribute__ ((visibility("hidden")))
#	else
#		define DAG_HELPER_DLL_IMPORT
#		define DAG_HELPER_DLL_EXPORT
#		define DAG_HELPER_DLL_LOCAL
#	endif
#endif

/* Use generic helper definitions above to define DAG_API and DAG_LOCAL.
 * DAG_API is used for the public API symbols. It either DLL imports or DLL 
 * exports (or does nothing for static build)
 * DAG_LOCAL is used for non-api symbols. */

/* defined if DAG is compiled as a DLL */
#ifdef DAG_DLL 
/* defined if we are building the DAG DLL (instead of using it) */
#	ifdef DAG_DLL_EXPORTS
#		define DAG_API DAG_HELPER_DLL_EXPORT
#	else
#		define DAG_API DAG_HELPER_DLL_IMPORT
#	endif // DAG_DLL_EXPORTS
#	define DAG_LOCAL DAG_HELPER_DLL_LOCAL
/* DAG_DLL is not defined: this means DAG is a static lib. */
#else 
#	define DAG_API
#	define DAG_LOCAL
#endif /* DAG_DLL */

#include <map>
#include <string>
#include <memory>
#include <vector>
#include <queue>
#include <list>
#include <set>

namespace dag {

	enum LearnResource {
		LearnResource_None = 0,
		LearnResource_Inputs = 1,
		LearnResource_Outputs = 2,
		LearnResource_InputsOutputs = 3
	};
	
	enum GraphErrorType {
		GraphError_GraphError = -1,
		GraphError_NonInvertibleNode = -2,
		GraphError_LoopInGraph = -3,
		GraphError_MissingHeadNode = -4,
		GraphError_MissingTailNode = -5,
		GraphError_MissingLearnResource = -6,
		GraphError_MissingInputs = -7,
		GraphError_MissingOutputs = -8,
		GraphError_MissingInputsAndOutputs = -9,
		GraphError_UnconnectedNode = -10,
		GraphError_AmbiguousConnection = -11,
		GraphError_HangingConnection = -12
		//GraphError_IncompatibleFrameSpec = -13
	};
	
	class DAG_API Edge;
	class DAG_API Node;
	class DAG_API FrameSpec;
	
	class DAG_API DagError {
	public:
		DagError(const std::string& msg);
		virtual ~DagError() throw();
		const std::string& what() const throw();
		
	private:
		std::string m_What;
	};
	
	class DAG_API NullPtr : public DagError {
	public:
		NullPtr();
		virtual ~NullPtr() throw();
	};
	
	class DAG_API InvalidArgument : public DagError {
	public:
		InvalidArgument();
		virtual ~InvalidArgument() throw();
	};
		
	class DAG_API NodeStateError : public DagError {
	public:
		NodeStateError();
		virtual ~NodeStateError() throw();
	};
	
	class DAG_API SerialError : public DagError {
	public:
		SerialError(const std::string& msg);
		virtual ~SerialError() throw();
	};
	
	class DAG_API InvalidValue : public SerialError {
	public:
		InvalidValue();
		virtual ~InvalidValue() throw();
	};
	
	class DAG_API InvalidToken : public SerialError {
	public:
		InvalidToken(const std::string& expectedToken,
					 const std::string& foundToken);
		virtual ~InvalidToken() throw();
		const std::string& expected() const;
		const std::string& found() const;
	private:
		std::string m_Expected;
		std::string m_Found;
	};

	class DAG_API FileError : public SerialError {
	public:
		FileError(const std::string& msg, const std::string& filename);
		virtual ~FileError() throw();
		const std::string& file() const throw();
	private:
		std::string m_Filename;
	};
	
	class DAG_API FileWriteError : public FileError {
	public:
		FileWriteError(const std::string& filename);
		virtual ~FileWriteError() throw();
	};
	
	class DAG_API InvalidFrameOpId : public SerialError {
	public:
		InvalidFrameOpId();
		virtual ~InvalidFrameOpId() throw();
	};
	
	class DAG_API FileReadError : public FileError {
	public:
		FileReadError(const std::string& filename);
		virtual ~FileReadError() throw();
	};

	/// \brief Describes the sizes of the various arrays of a frame
	///
	/// \sa Frame
	struct DAG_API FrameSpec {
		/// Creates a FrameSpec, initializing all values to 0
		FrameSpec();
		/// Creates a FrameSpec with specific sizes
		FrameSpec(size_t numBools, 
				  size_t numInts, 
				  size_t numFloats, 
				  size_t numDoubles);
		
		/// \brief Compare if two FrameSpecs are equal
		///
		/// \return True if all sizes are equal, false otherwise
		bool operator==(const FrameSpec& rhs) const;
		/// \brief Compare if two FrameSpecs are unequal
		///
		/// \return True if any sizes are unequal, false otherwise
		bool operator!=(const FrameSpec& rhs) const;
		
		/// Size of boolean array
		size_t bSize;
		/// Size of integer array
		size_t iSize;
		/// Size of float array
		size_t fSize;
		/// Size of double array
		size_t dSize;
		
		/// \brief Serializes a FrameSpec into a binary stream
		virtual void serialize(std::ostream& binaryStream) const;
		
		/// \brief Creates a FrameSpec from a binary stream
		///
		/// The caller of this function is responsible for deleting the
		/// FrameSpec either via the auto_ptr's destructor or by releasing it
		/// from the auto_ptr and explicitly calling delete
		///
		/// \return Auto-pointer to FrameSpec. 
		static std::auto_ptr<FrameSpec> inflate(std::istream& binaryStream);
		
		virtual void load(std::istream& binaryStream);
	private:
		
	};
	
	class DAG_API UnequalFrameSpec : public DagError {
		UnequalFrameSpec();
	public:
		UnequalFrameSpec(const FrameSpec& spec1, const FrameSpec& spec2);
		virtual ~UnequalFrameSpec() throw();
		const FrameSpec& frameSpec1() const;
		const FrameSpec& frameSpec2() const;
		
	private:
		FrameSpec m_Spec1;
		FrameSpec m_Spec2;
	};
	
	/// \brief Holds a single instance of intrinsic values.  
	/// \sa FrameSpec
	class DAG_API Frame : public FrameSpec {

		
	public:
		Frame();
		/// \brief Construct from a FrameSpec.  
		///
		/// All arrays will be allocated to the sizes described in the FrameSpec
		Frame(const FrameSpec& spec);
		
		/// \brief Construct from arguments
		///
		/// All arrays will be allocated to the sizes described in the arguments
		Frame(size_t numBools, 
			  size_t numInts, 
			  size_t numFloats, 
			  size_t numDoubles);
		
		/// \brief Construct by copying
		Frame(const Frame& copy);
		
		/// \brief Destruct Frame
		virtual	~Frame();
		
		/// \brief Set this Frame equal to another
		Frame& operator=(const Frame& rhs);
		
		void resize(const FrameSpec& spec);

		/// \brief Boolean array
		/// \sa bSize
		bool*	bools;
		/// \brief Integer array
		/// \sa iSize
		int*	ints;
		/// \brief Float array
		/// \sa fSize
		float*	floats;
		/// \brief Double array
		/// \sa dSize
		double*	doubles;
		
		/// \brief Serializes a Frame into a binary stream
		virtual void serialize(std::ostream& binaryStream) const;
		
		/// \brief Creates a Frame from a binary stream
		///
		/// The caller of this function is responsible for deleting the
		/// Frame either via the auto_ptr's destructor or by releasing it
		/// from the auto_ptr and explicitly calling delete
		///
		/// \return Auto-pointer to Frame. 
		static std::auto_ptr<Frame> inflate(std::istream& binaryStream);
		
		virtual void load(std::istream& binaryStream);
		
	private:
	};
	
	class DAG_API FrameDepot {
		FrameDepot();
	public:
		FrameDepot(const FrameSpec& spec);
		FrameDepot(const FrameDepot& copy);
		virtual ~FrameDepot() throw();
		
		FrameDepot& operator=(const FrameDepot& rhs);
		
		size_t size() const;
		void clear() ;

		const bool* bools(size_t pos) const;
		const int* ints(size_t pos) const;
		const float* floats(size_t pos) const;
		const double* doubles(size_t pos) const;
		
		const FrameSpec& spec() const;
		void fill(size_t pos, Frame& frame) const;
		
		void add(const Frame& frame);
		
		/// \brief Serializes a FrameDepot into a binary stream
		virtual void serialize(std::ostream& binaryStream) const;
		
		/// \brief Creates a FrameDepot from a binary stream
		///
		/// The caller of this function is responsible for deleting the
		/// FrameDepot either via the auto_ptr's destructor or by releasing it
		/// from the auto_ptr and explicitly calling delete
		///
		/// \return Auto-pointer to FrameDepot. 
		static std::auto_ptr<FrameDepot> inflate(std::istream& binaryStream);
		
		
	private:

		std::vector<bool*> m_BData;
		std::vector<int*> m_IData;
		std::vector<float*> m_FData;
		std::vector<double*> m_DData;
		size_t m_NumFrames;
		
		FrameSpec m_Spec;
	};
	
	class DAG_API FrameOperator {
	public:
		
		FrameOperator(const std::string& opName = std::string(""));
		virtual ~FrameOperator() throw();
		virtual void operate(const Frame& input, Frame& output) = 0;
		void serialize(const std::string& filename) const;
		static std::auto_ptr<FrameOperator> 
			inflate(const std::string& filename);
		void serialize(std::ostream& binaryStream) const;
		static std::auto_ptr<FrameOperator> inflate(std::istream& binaryStream);
		const std::string& name() const;
		void name(const std::string& opName);
	protected:
		
	private:
		std::string m_Name;
		virtual const std::string& _frameOperatorID() const = 0;
		virtual void _serialize(std::ostream& binaryStream) const = 0;
	};
	
	typedef std::auto_ptr<FrameOperator> (*frame_op_inflate_fp)(std::istream& bStream);
	void DAG_API register_inflator(const char* opId, 
								   frame_op_inflate_fp inflateFunc);

	class DAG_API NodeProgressListener {
	public:
		NodeProgressListener(){}
		virtual ~NodeProgressListener(){}
		virtual void progress(const std::string& msg, double progress) = 0;
	};
	
	struct DAG_API NodeTraits {
		bool canReverse;
		LearnResource resources;
	};
	
	class DAG_API Node {
		Node(const Node& copy);
		Node& operator=(const Node& rhs);

	public:
		Node();
		virtual ~Node() throw();
		
		void init(const FrameSpec& expectedInputSpec);
		void learn(const FrameDepot* input, 
				   const FrameDepot* output);
		const FrameSpec& inputSpec() const;
		const FrameSpec& outputSpec() const;
		std::auto_ptr<FrameOperator> createForwardOp() const;
		std::auto_ptr<FrameOperator> createReverseOp() const;
		void addListener(NodeProgressListener* listener);
		void removeListener(NodeProgressListener* listener);
		
		virtual const NodeTraits& traits() const = 0;
		
	protected:
		void resetNodeState();
		void updateLearnProgress(const std::string& msg, double progress) const;
		virtual void _init(const FrameSpec& expectedInputSpec) = 0;
		virtual const FrameSpec& _inputSpec() const = 0;
		virtual const FrameSpec& _outputSpec() const = 0;
		virtual std::auto_ptr<FrameOperator> _createForwardOp() const = 0;
		virtual std::auto_ptr<FrameOperator> _createReverseOp() const;
		virtual void _learn(const FrameDepot* input, 
							const FrameDepot* output) = 0;
	private:
		enum NodeState
		{
			NodeState_NotInitialized = 0,
			NodeState_Initialized = 1,
			NodeState_Taught = 2
		};
		
		NodeState m_CurrentState;
		std::set<NodeProgressListener*> m_Listeners;
	};
	
	class DAG_API Link {
	public:
		static const size_t unconnected;
		Link();
		static Link fromRanges(size_t numRanges, ...);
		static Link fromConnections(size_t numConnections, ...);
		static Link fullyConnected(size_t numConnections);
		static Link fromArrays(size_t len, 
							   const size_t* forwardArray,
							   const size_t* reverseArray);
							   
		
		
		Link& link(size_t inputPos, size_t outputPos);
		Link& unlink(size_t inputPos, size_t outputPos);
		Link& linkRange(size_t inputStart, size_t inputEnd,
						size_t outputStart, size_t outputEnd);
		Link& unlinkRange(size_t inputStart, size_t inputEnd,
						  size_t outputStart, size_t outputEnd);
		void clear();
		size_t forward(size_t inputPos) const;
		size_t reverse(size_t outputPos) const;
		
		const std::set<size_t>& forwardSet() const;
		const std::set<size_t>& reverseSet() const;
		
		size_t size() const;
		void asArrays(size_t* forwardArray, size_t* reverseArray) const;
				
		void serialize(std::ostream& binaryStream) const;
		static Link inflate(std::istream& binaryStream);
	private:
		std::set<size_t> m_ForIdxs;
		std::set<size_t> m_RevIdxs;
		std::map<size_t, size_t> m_ForMap;
		std::map<size_t, size_t> m_RevMap;
	};
	
	class DAG_API FrameLink {
	public:
		FrameLink();
		FrameLink(const Link& boolLink,
				   const Link& intLink,
				   const Link& floatLink,
				   const Link& doubleLink);
		
		void serialize(std::ostream& binaryStream) const;
		static FrameLink inflate(std::istream& binaryStream);
		
		Link bLink;
		Link iLink;
		Link fLink;
		Link dLink;
		
	};
	
	class DAG_API Edge {
	public:
		Edge(Node* fromNode, const FrameLink& nodeLink, Node* toNode);
		Node* from;
		FrameLink link;
		Node* to;
	};
	
	class DAG_API Graph {
	public:
		Graph();
		Graph(const Graph& copy);
		Graph& operator=(const Graph& rhs);
		
		virtual ~Graph() throw();
		void setHeadNode(Node& node);
		void setTailNode(Node& node);
		void addNode(Node& node);
		void connect(const Edge& edge); 
		void removeNode(Node& node);
		bool disconnect(Node* from, Node* to);
		
		Node* getHeadNode();
		const Node* getHeadNode() const;
		Node* getTailNode();
		const Node* getTailNode() const;
		const std::set<Node*>& getNodes() const;
		
		std::vector<Edge*> getOutputEdges(const Node* node) const;
		std::vector<Edge*> getInputEdges(const Node* node) const;
		
	protected:
		void freeEdges(std::vector<Edge*>& edgeVec);
		bool freeEdge(Edge* edge);
		
	private:
		void freeMemory();
		Node* m_HeadNode;
		Node* m_TailNode;
		std::set<Node*> m_Nodes;
		std::set<Edge*> m_Edges;
		std::map<const Node*, std::vector<Edge*>* > m_OutputEdges;
		std::map<const Node*, std::vector<Edge*>* > m_InputEdges;
		
	};
	
	class DAG_API GraphError : public DagError {
	public:
		GraphError(const std::string& msg, 
				   const Node* node, 
				   const std::set<const Node*>& nodes,
				   const std::vector<Edge*>& edges,
				   const FrameSpec& spec,
				   GraphErrorType errorType);
		virtual ~GraphError() throw();
		const Node* getNode() const throw();
		const std::set<const Node*>& getNodes() const throw();
		const std::vector<Edge*>& getEdges() const throw();
		const FrameSpec& spec() const throw();
		GraphErrorType getGraphErrorType() const throw();
		
	private:
		std::vector<Edge*> m_Edges;
		const Node* m_Node;
		std::set<const Node*> m_Nodes;
		FrameSpec m_FrameSpec;
		GraphErrorType m_ErrorType;
	};
	
	class DAG_API NonInvertibleNode : public GraphError {
	public:
		NonInvertibleNode(const Node* node);
		virtual ~NonInvertibleNode() throw();
	};
	
	class DAG_API LoopError : public GraphError {
	public:
		LoopError(const std::set<const Node*>& nodes);
		virtual ~LoopError() throw();
	};
	
	class DAG_API MissingHeadNode : public GraphError {
	public:
		MissingHeadNode();
		virtual ~MissingHeadNode() throw();
	};
	
	class DAG_API MissingTailNode : public GraphError {
	public:
		MissingTailNode();
		virtual ~MissingTailNode() throw();
	};
	
	class DAG_API MissingLearnResource : public GraphError {
	public:
		MissingLearnResource(const std::string& msg, 
							 const Node* node,
							 GraphErrorType errorType = GraphError_MissingLearnResource);
		virtual ~MissingLearnResource() throw();
	};
	
	class DAG_API MissingInputs : public MissingLearnResource {
	public:
		MissingInputs(const Node* node);
		virtual ~MissingInputs() throw();
	};
	
	class DAG_API MissingOutputs : public MissingLearnResource {
	public:
		MissingOutputs(const Node* node);
		virtual ~MissingOutputs() throw();
	};
	
	class DAG_API MissingInputsAndOutputs : public MissingLearnResource {
	public:
		MissingInputsAndOutputs(const Node* node);
		virtual ~MissingInputsAndOutputs() throw();
	};
	
	class DAG_API UnconnectedNode : public GraphError {
	public:
		UnconnectedNode(const Node* node);
		virtual ~UnconnectedNode() throw();
	};
	
	class DAG_API AmbiguousConnection : public GraphError {
	public:
		AmbiguousConnection(const std::vector<Edge*>& edgeVec);
		virtual ~AmbiguousConnection() throw();
	};
	
	class DAG_API HangingConnection : public GraphError {
	public:
		HangingConnection(const Node* node,
						  const std::vector<Edge*>& edgeVec);
		virtual ~HangingConnection() throw();
	};
	
//	class DAG_API IncompatibleFrameSpec : public GraphError {
//	public:
//		IncompatibleFrameSpec(const std::vector<Edge*>& edgeVec,
//							  const Node* node,
//							  const FrameSpec& spec);
//		virtual ~IncompatibleFrameSpec() throw();
//	};
	
	DAG_API void validate(const Graph& graph, 
				  const FrameSpec& expectedInputSpec, 
				  FrameSpec& outputSpec,
				  std::list<GraphError>& errors);
	
	DAG_API void validate_links(const Graph& graph,
							  const FrameSpec& expectedInputSpec,
							  FrameSpec& outputSpec,
							  std::list<GraphError>& errors);
	
	DAG_API void validate_resources(const Graph& graph,
							std::list<GraphError>& errors);
	
	DAG_API bool initialize(const Graph& graph,
					const FrameSpec& expectedInputSpec,
					FrameSpec& outputSpec,
					std::list<GraphError>& errors);
	
	DAG_API void find_loops(const Graph& graph, 
					std::list<std::set<const Node*> >& loops);
	
	DAG_API NodeTraits get_node_set_traits(const std::set<Node*>& nodes);
	
	class DAG_API GraphNode : public Node {
		
	public:

		GraphNode();
		virtual ~GraphNode() throw();
		const Graph* getGraph() const;
		void setGraph(const Graph& graph);		
		const NodeTraits& traits() const;
		
	protected:
		

		void _init(const FrameSpec& expectedInputSpec);
		const FrameSpec& _inputSpec() const;
		const FrameSpec& _outputSpec() const;
		std::auto_ptr<FrameOperator> _createForwardOp() const;
		std::auto_ptr<FrameOperator> _createReverseOp() const;
		void _learn(const FrameDepot* input, const FrameDepot* output);
		
	private:

		std::auto_ptr<Graph> m_Graph;
		NodeTraits m_NodeTraits;
		FrameSpec m_InputSpec;
		FrameSpec m_OutputSpec;
	};
}
#endif